/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.graph.test;

import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.graph.*;
import org.apache.jena.rdf.model.impl.ReifierStd;
import org.apache.jena.shared.AlreadyReifiedException;
import org.apache.jena.shared.CannotReifyException;
import org.apache.jena.vocabulary.RDF;

/**
 * Abstract base class for reification tests.
 */
public abstract class AbstractTestReifier extends GraphTestBase {
    protected static final Triple ALL = Triple.ANY;

    public AbstractTestReifier(String name) {
        super(name);
    }

    public abstract Graph getGraph();

    protected final Graph getGraphWith(String facts) {
        Graph result = getGraph();
        graphAdd(result, facts);
        return result;
    }

    /**
     * Answer the empty graph if cond is false, otherwise the graph with the given
     * facts.
     */
    protected final Graph graphWithUnless(boolean cond, String facts) {
        return graphWith(cond ? "" : facts);
    }

    protected final Graph graphWithIf(boolean cond, String facts) {
        return graphWithUnless(!cond, facts);
    }

    public void testGetGraphNotNull() {
        assertNotNull(getGraph());
    }

    /**
     * Check that the standard reifier will note, but not hide, reification quads.
     */
    public void testStandard() {
        Graph g = getGraph();
        assertFalse(ReifierStd.hasTriple(g, triple("s p o")));
        g.add(NodeCreateUtils.createTriple("x rdf:subject s"));
        assertEquals(1, g.size());
        g.add(NodeCreateUtils.createTriple("x rdf:predicate p"));
        assertEquals(2, g.size());
        g.add(NodeCreateUtils.createTriple("x rdf:object o"));
        assertEquals(3, g.size());
        g.add(NodeCreateUtils.createTriple("x rdf:type rdf:Statement"));
        assertEquals(4, g.size());
        assertTrue(ReifierStd.hasTriple(g, triple("s p o")));
    }

    /**
     * Test that the Standard reifier will expose implicit quads arising from
     * reifyAs().
     */
    public void testStandardExplode() {
        Graph g = getGraph();
        ReifierStd.reifyAs(g, node("a"), triple("p Q r"));
        Graph r = GraphMemFactory.createDefaultGraph();
        graphAdd(r, "a rdf:type rdf:Statement; a rdf:subject p; a rdf:predicate Q; a rdf:object r");
        assertEquals(4, g.size());
        assertIsomorphic(r, g);
    }

    /**
     * Ensure that over-specifying a reification means that we don't get a triple
     * back. Goodness knows why this test wasn't in right from the beginning.
     */
    public void testOverspecificationSuppressesReification() {
        Graph g = getGraph();
        graphAdd(g, "x rdf:subject A; x rdf:predicate P; x rdf:object O; x rdf:type rdf:Statement");
        assertEquals(triple("A P O"), ReifierStd.getTriple(g, node("x")));
        graphAdd(g, "x rdf:subject BOOM");
        assertEquals(null, ReifierStd.getTriple(g, node("x")));
    }

    public void testReificationSubjectClash() {
        testReificationClash("x rdf:subject SS");
    }

    public void testReificationPredicateClash() {
        testReificationClash("x rdf:predicate PP");
    }

    public void testReificationObjectClash() {
        testReificationClash("x rdf:object OO");
    }

    protected void testReificationClash(String clashingStatement) {
        Graph g = getGraph();
        Triple SPO = NodeCreateUtils.createTriple("S P O");
        ReifierStd.reifyAs(g, node("x"), SPO);
        assertTrue(ReifierStd.hasTriple(g, SPO));
        graphAdd(g, clashingStatement);
        assertEquals(null, ReifierStd.getTriple(g, node("x")));
        assertFalse(ReifierStd.hasTriple(g, SPO));
    }

    /**
     * Test that reifying a triple explicitly has some effect on the graph only for
     * Standard reifiers.
     */
    public void testManifestQuads() {
        Graph g = getGraph();
        ReifierStd.reifyAs(g, node("A"), triple("S P O"));
        String reified = "A rdf:type rdf:Statement; A rdf:subject S; A rdf:predicate P; A rdf:object O";
        assertIsomorphic(graphWith(reified), g);
    }

    public void testHiddenVsReification() {
        Graph g = getGraph();
        ReifierStd.reifyAs(g, node("A"), triple("S P O"));
        assertTrue(ReifierStd.findEither(g, ALL, false).hasNext());
    }

    public void testRetrieveTriplesByNode() {
        Graph G = getGraph();
        Node N = NodeFactory.createBlankNode(), M = NodeFactory.createBlankNode();
        ReifierStd.reifyAs(G, N, triple("x R y"));
        assertEquals("gets correct triple", triple("x R y"), ReifierStd.getTriple(G, N));
        ReifierStd.reifyAs(G, M, triple("p S q"));
        assertDiffer("the anon nodes must be distinct", N, M);
        assertEquals("gets correct triple", triple("p S q"), ReifierStd.getTriple(G, M));

        assertTrue("node is known bound", ReifierStd.hasTriple(G, M));
        assertTrue("node is known bound", ReifierStd.hasTriple(G, N));
        assertFalse("node is known unbound", ReifierStd.hasTriple(G, NodeFactory.createURI("any:thing")));
    }

    public void testRetrieveTriplesByTriple() {
        Graph G = getGraph();
        Triple T = triple("x R y"), T2 = triple("y R x");
        Node N = node("someNode");
        ReifierStd.reifyAs(G, N, T);
        assertTrue("R must have T", ReifierStd.hasTriple(G, T));
        assertFalse("R must not have T2", ReifierStd.hasTriple(G, T2));
    }

    public void testReifyAs() {
        Graph G = getGraph();
        Node X = NodeFactory.createURI("some:uri");
        assertEquals("node used", X, ReifierStd.reifyAs(G, X, triple("x R y")));
        assertEquals("retrieves correctly", triple("x R y"), ReifierStd.getTriple(G, X));
    }

    public void testAllNodes() {
        Graph G = getGraph();
        ReifierStd.reifyAs(G, node("x"), triple("cows eat grass"));
        ReifierStd.reifyAs(G, node("y"), triple("pigs can fly"));
        ReifierStd.reifyAs(G, node("z"), triple("dogs may bark"));
        assertEquals("", nodeSet("z y x"), Iter.toSet(ReifierStd.allNodes(G)));
    }

    public void testRemoveByNode() {
        Graph G = getGraph();
        Node X = node("x"), Y = node("y");
        ReifierStd.reifyAs(G, X, triple("x R a"));
        ReifierStd.reifyAs(G, Y, triple("y R a"));
        ReifierStd.remove(G, X, triple("x R a"));
        assertFalse("triple X has gone", ReifierStd.hasTriple(G, X));
        assertEquals("triple Y still there", triple("y R a"), ReifierStd.getTriple(G, Y));
    }

    public void testRemoveFromNothing() {
        Graph G = getGraph();
        G.delete(triple("quint rdf:subject S"));
    }

    public void testException() {
        Graph G = getGraph();
        Node X = node("x");
        ReifierStd.reifyAs(G, X, triple("x R y"));
        ReifierStd.reifyAs(G, X, triple("x R y"));
        try {
            ReifierStd.reifyAs(G, X, triple("x R z"));
            fail("did not detect already reified node");
        } catch (AlreadyReifiedException e) {}
    }

    public void testKevinCaseA() {
        Graph G = getGraph();
        Node X = node("x"), a = node("a"), b = node("b"), c = node("c");
        G.add(Triple.create(X, RDF.Nodes.type, RDF.Nodes.Statement));
        ReifierStd.reifyAs(G, X, Triple.create(a, b, c));
    }

    public void testKevinCaseB() {
        Graph G = getGraph();
        Node X = node("x"), Y = node("y");
        Node a = node("a"), b = node("b"), c = node("c");
        G.add(Triple.create(X, RDF.Nodes.subject, Y));
        try {
            ReifierStd.reifyAs(G, X, Triple.create(a, b, c));
            fail("X already has subject Y: cannot make it a");
        } catch (CannotReifyException e) {
            pass();
        }
    }

    public void testQuadRemove() {
        Graph g = getGraph();
        assertEquals(0, g.size());
        Triple s = NodeCreateUtils.createTriple("x rdf:subject s");
        Triple p = NodeCreateUtils.createTriple("x rdf:predicate p");
        Triple o = NodeCreateUtils.createTriple("x rdf:object o");
        Triple t = NodeCreateUtils.createTriple("x rdf:type rdf:Statement");
        g.add(s);
        g.add(p);
        g.add(o);
        g.add(t);
        assertEquals(4, g.size());
        g.delete(s);
        g.delete(p);
        g.delete(o);
        g.delete(t);
        assertEquals(0, g.size());
    }

    public void testEmpty() {
        Graph g = getGraph();
        assertTrue(g.isEmpty());
        graphAdd(g, "x rdf:type rdf:Statement");
        assertFalse(g.isEmpty());
        graphAdd(g, "x rdf:subject Deconstruction");
        assertFalse(g.isEmpty());
        graphAdd(g, "x rdf:predicate rdfs:subTypeOf");
        assertFalse(g.isEmpty());
        graphAdd(g, "x rdf:object LiteraryCriticism");
        assertFalse(g.isEmpty());
    }

    public void testReifierEmptyFind() {
        Graph g = getGraph();
        assertEquals(tripleSet(""), ReifierStd.findExposed(g, Triple.ANY).toSet());
    }

    public void testReifierFindSubject() {
        testReifierFind("x rdf:subject S");
    }

    public void testReifierFindObject() {
        testReifierFind("x rdf:object O");
    }

    public void testReifierFindPredicate() {
        testReifierFind("x rdf:predicate P");
    }

    public void testReifierFindComplete() {
        testReifierFind("x rdf:predicate P; x rdf:subject S; x rdf:object O; x rdf:type rdf:Statement");
    }

    public void testReifierFindFilter() {
        Graph g = getGraph();
        graphAdd(g, "s rdf:subject S");
        assertEquals(tripleSet(""), ReifierStd.findExposed(g, triple("s otherPredicate S")).toSet());
    }

    protected void testReifierFind(String triples) {
        testReifierFind(triples, "?? ?? ??");
    }

    protected void testReifierFind(String triples, String pattern) {
        Graph g = getGraph();
        graphAdd(g, triples);
        assertEquals(tripleSet(triples), ReifierStd.findExposed(g, triple(pattern)).toSet());
    }
}
